package net.helix.core.transform;

import java.nio.FloatBuffer;

import net.helix.graphics.GraphicsHelper;

import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;
import org.lwjgl.util.vector.Matrix4f;
import org.lwjgl.util.vector.Vector3f;

/**
 * 
 * @author Robert Hewitt
 * Contains properties for a 4x4 transformation matrix.
 */
public class Transform {
	/**
	 * x, y, and z offset applied after matrix transformation
	 */
	private float x = 0,y = 0,z = 0;
	public float getX() {
		return x;
	}
	public float getY() {
		return y;
	}
	public float getZ() {
		return z;
	}
	/**
	 * Scale applied to matrix transformation
	 */
	private float scaleX = 1, scaleY = 1, scaleZ = 1;
	public float getScaleX() {
		return scaleX;
	}
	public float getScaleY() {
		return scaleY;
	}
	public float getScaleZ() {
		return scaleZ;
	}
	/**
	 * Rotation applied after matrix transformation
	 */
	private float rotX = 0, rotY = 0, rotZ = 0;
	public float getRotX() {
		return rotX;
	}
	public float getRotY() {
		return rotY;
	}
	public float getRotZ() {
		return rotZ;
	}
	/**
	 * When false allows x, y, z, scaleX, scaleY, scaleZ, rotX, rotY, and rotZ
	 * to determine the calculation of the matrix.<br>
	 * After set to true the matrix will no longer be recalculated, allowing the 
	 * raw values in the matrix to be manually set.
	 */
	public boolean useOnlyMatrix = false;
	/**
	 *  The matrix to use in the transformations
	 */
	public Matrix4f matrix4f;
	
	public FloatBuffer matrixBuffer;
	
	/**
	 * Used to determine if the matrix needs to be recalculated.
	 */
	private boolean isDirty = true;
	
	/**
	 * Determines whether or not to use 2d or 3d coordinates.
	 */
	private TransformDimension dimension;
	/**
	 * Creates a new Transform object. Default to 3d.
	 */
	public Transform(){
		this(TransformDimension.THREE);
	}
	/**
	 * Creates a new Transform object.
	 * @param dimension - The dimension it will be set to.
	 */
	public Transform(TransformDimension dimension){
		this.dimension = dimension;
		
		matrix4f = new Matrix4f();
		matrixBuffer = BufferUtils.createFloatBuffer(16);
		matrixBuffer.clear();
		matrix4f.store(matrixBuffer);
	}
	/**
	 * Forces the matrix to be recalculated right now.
	 */
	static Vector3f v100 = new Vector3f(1,0,0);
	static Vector3f v010 = new Vector3f(0,1,0);
	static Vector3f v = new Vector3f();
	public void recalculateMatrix(){
		// TODO: something else done here when 2d instead of 3d
		/*
		matrix[0] = scaleX; matrix[1] = 0; matrix[2] = 0; matrix[3] = 0;
		matrix[4] = 0; matrix[5] = scaleY; matrix[6] = 0; matrix[7] = 0;
		matrix[8] = 0; matrix[9] = 0; matrix[10] = scaleZ; matrix[11] = 0;
		matrix[12] = 0; matrix[13] = 0; matrix[14] = 0; matrix[15] = 1;
		
		matrixBuffer.clear();
		matrixBuffer.put(matrix).flip();
		*/
		
		if(useOnlyMatrix == false){
			//Matrix4f.setIdentity(matrix4f);
			
			matrix4f.m00 = scaleX; matrix4f.m01 = 0;      matrix4f.m02 = 0;      matrix4f.m03 = 0;
			matrix4f.m10 = 0;      matrix4f.m11 = scaleY; matrix4f.m12 = 0;      matrix4f.m13 = 0;
			matrix4f.m20 = 0;      matrix4f.m21 = 0;      matrix4f.m22 = scaleZ; matrix4f.m23 = 0;
			matrix4f.m30 = 0;      matrix4f.m31 = 0;      matrix4f.m32 = 0;      matrix4f.m33 = 1;
			
			
			//matrix4f.rotate(rotY, new Vector3f(1,0,0));
			//matrix4f.rotate(rotX, new Vector3f(0,1,0));
			
			//matrix4f.rotate(rotZ, new Vector3f(1,0,0));
			
			//matrix4f.translate(new Vector3f(x, y, z));
			
			matrix4f.rotate((float) (rotY*Math.PI/180), v100);
			matrix4f.rotate((float) (rotX*Math.PI/180), v010);
			
			v.x = x;
			v.y = y;
			v.z = z;
			
			matrix4f.translate(v);			
		}
		
		matrixBuffer.clear();
		matrix4f.store(matrixBuffer);
		matrixBuffer.flip();
		
		isDirty = false;
	}
	/*
	public void scale(float f){
		scaleX *= f;
		scaleY *= f;
		scaleZ *= f;
		isDirty = true;
	}
	*/
	/**
	 * Set transformation values for the matrix.
	 * @param x
	 * @param y
	 * @param z
	 */
	public void setTranslate(float x, float y, float z){
		this.x = x;
		this.y = y;
		this.z = z;
		isDirty = true;
	}
	/**
	 * Adds transformation values for the matrix.
	 * @param x
	 * @param y
	 * @param z
	 */
	public void translate(float x, float y, float z){
		this.x += x;
		this.y += y;
		this.z += z;
		isDirty = true;
	}
	/**
	 * Set the scale values for the matrix.
	 * @param sx
	 * @param sy
	 * @param sz
	 */
	public void setScale(float sx, float sy, float sz){
		scaleX = sx;
		scaleY = sy;
		scaleZ = sz;
		isDirty = true;
	}
	/**
	 * Scale values for the matrix.
	 * @param sx
	 * @param sy
	 * @param sz
	 */
	public void scale(float sx, float sy, float sz){
		scaleX *= sx;
		scaleY *= sy;
		scaleZ *= sz;
		isDirty = true;
	}
	/**
	 * Set rotation values for the matrix in world coordinates.
	 * @param x
	 * @param y
	 * @param z
	 */
	public void setRotate(float x, float y, float z){
		this.rotX = x;
		this.rotY = y;
		this.rotZ = z;
		isDirty = true;
	}
	boolean stackedMatrix = false;
	boolean transformed = false;
	/**
	 * Applies the current transformation to openGL by doing a matrix multiply.
	 */
	public void apply(){
		apply(false);
	}
	public void apply(boolean stackMatrix){
		stackedMatrix = stackMatrix;
		if(isDirty) recalculateMatrix();
		// apply the matrix
		
		/*
		GL11.glRotatef(rotY, 1, 0, 0);
		GL11.glRotatef(rotX, 0, 1, 0);
		
		
		GL11.glTranslatef(x, y, z);
		*/
		
		if(stackedMatrix){
			if(transformed == true){
				throw new Error("Can't apply the same transformation twice in a row!");
			}
			GraphicsHelper.applyMatrix(matrix4f, matrixBuffer);
			transformed = true;
		}else{
			GL11.glMultMatrix(matrixBuffer);
		}
	}
	public void unApply(){
		if(stackedMatrix){
			if(transformed == false){
				throw new Error("Can't unApply the same transformation twice in a row!");
			}
			GraphicsHelper.unApplyMatrix();
			transformed = false;
		}
	}
	/**
	 * Sets the dimension and forces the matrix to be recalculated next time <tt>apply()</tt> is called.
	 * @param dimension
	 */
	public void setDimension(TransformDimension dimension){
		this.dimension = dimension;
		isDirty = true;
	}
	/**
	 * Gets the dimension of the transformation.
	 */
	public TransformDimension getDimension(){
		return dimension;
	}
	
	// -------------------
	

	public void setIsoProjection(float l, float r, float b, float t, float n, float f){

		useOnlyMatrix = true;
		
		matrix4f.m00 = 2/(r-l); matrix4f.m01 = 0;       matrix4f.m02 = 0;        matrix4f.m03 = -(r+l)/(r-l);
		matrix4f.m10 = 0;       matrix4f.m11 = 2/(t-b); matrix4f.m12 = 0;        matrix4f.m13 = -(t+b)/(t-b);
		matrix4f.m20 = 0;       matrix4f.m21 = 0;       matrix4f.m22 = -2/(f-n); matrix4f.m23 = -(f+n)/(f-n);
		matrix4f.m30 = 0;       matrix4f.m31 = 0;       matrix4f.m32 = 0;        matrix4f.m33 = 1;

		matrixBuffer.clear();
		matrix4f.store(matrixBuffer);
		matrixBuffer.flip();
		
		isDirty = false;
	}
	public void setProjection(float l, float r, float b, float t, float n, float f){

		useOnlyMatrix = true;

		matrix4f.m00 = (2*n)/(r-l); matrix4f.m01 = 0;           matrix4f.m02 = (r+l)/(r-l);  matrix4f.m03 = 0;
		matrix4f.m10 = 0;           matrix4f.m11 = (2*n)/(t-b); matrix4f.m12 = (t+b)/(t-b);  matrix4f.m13 = 0;
		matrix4f.m20 = 0;           matrix4f.m21 = 0;           matrix4f.m22 = -(f+n)/(f-n); matrix4f.m23 = -(2*f*n)/(f-n);
		matrix4f.m30 = 0;           matrix4f.m31 = 0;           matrix4f.m32 = -1;           matrix4f.m33 = 0;

		matrixBuffer.clear();
		matrix4f.store(matrixBuffer);
		matrixBuffer.flip();
		
		isDirty = false;
	}
	
	
}
